package com.huaxindata.im.main.chat.mp3recorder.netnat;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.BUFFSERSIZE;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.C_PORT;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.MAGIC;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.NetNatPacket;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.NetState;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.S_ADDR;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.S_PORT;
import static com.huaxindata.im.main.chat.mp3recorder.netnat.NetNatConfig.Type;


/**
 * 客户端代码
 */
public class NetNatSender implements Runnable {

    /**
     * 当前聊天的网络对象
     */
    LinkedHashMap<String, NetNatConfig.NetState> netStateCache = new LinkedHashMap<>();
    /**
     * 部分发包缓存
     */
    LinkedHashMap<String, DatagramPacket> netPacketCache = new LinkedHashMap<>();

    private List<NetNatConfig.NetNatPacket> packets = null;
    private boolean sending = false;

    DatagramSocket socket;
    ByteBuffer buffer;
    long lastSend = System.currentTimeMillis();

    public NetNatSender(DatagramSocket socket) {
        this.packets = Collections.synchronizedList(new LinkedList<NetNatConfig.NetNatPacket>());
        this.socket = socket;
        this.buffer = ByteBuffer.allocate(BUFFSERSIZE);
        this.lastSend = System.currentTimeMillis();
    }

    public void startSender() {
        sending = true;
        netPacketCache.clear();
        netStateCache.clear();
        new Thread(this).start();
    }

    public void stopSender() {
        sending = false;
        netPacketCache.clear();
        netStateCache.clear();
        socket = null;
    }

    @Override
    public void run() {
        boolean magicVaild;

        while (sending && !Thread.interrupted()) {
            while (packets.size() > 0) {
                NetNatPacket packet = packets.remove(0);
                // 获取数据
                final InetAddress remote = packet.remote;
                final ByteBuffer data = ByteBuffer.wrap(packet.data, 0, packet.size);
                // 判断magic
                magicVaild = true;
                final byte[] magic = get(data, MAGIC.length);
                for (int i = 0; i < MAGIC.length; i++) {
                    if (magic[i] != MAGIC[i]) {
                        System.err.println("Invalid message!" + String.valueOf(magic));
                        magicVaild = false;
                        continue;
                    }
                }
                if (!magicVaild) {
                    continue;
                }
                // 获取类型
                int type = data.get();
                /**
                 * 第一个流程为： 客户端发起获取连接信息的请求 - 服务器回执连接信息的请求
                 */
                if (type == Type.REQUEST || type == Type.FORWARD) {
                    //TODO not need to process the packet that send to Server
                } else if (type == Type.REQUEST_REPLY) {
                    try {
                        handRequest(data);
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                } else if (type == Type.NAT_S || type == Type.NAT_N) {
                    handNat(type == Type.NAT_S);
                } else if (type == Type.NAT_S_REPLY || type == Type.NAT_N_REPLY) {
                    //NAT 成功,可以开始传输消息
                } else if (type == Type.VOICE_DATA) {

                }
            }
        }
    }

    public void addPacket(InetAddress remote, byte[] data, int size) {
        NetNatPacket packet = new NetNatPacket();
        packet.remote = remote;
        packet.size = size;
        byte[] tempData = new byte[size];
        System.arraycopy(data, 0, tempData, 0, size);
        packet.data = tempData;
        packets.add(packet);
    }

    private boolean sendPacketFromCache(final String key) {
        if (netPacketCache.containsKey(key)) {
            DatagramPacket packet = netPacketCache.get(key);
            try {
                socket.send(packet);
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * 客户端发起获取连接信息的请求
     *
     * @throws UnknownHostException
     */
    public void sendRequest(String sender, String recver, String senderIp) throws Exception {
        final String key = NetNatConfig.Type.REQUEST + sender + recver;
        // getBitmap from cache first
        if (sendPacketFromCache(key)) {
            return;
        }
        buffer.clear();

        buffer.put(MAGIC);
        buffer.put(NetNatConfig.Type.REQUEST);

        buffer.put((byte) (sender.length() & 0xFF));
        buffer.put((byte) (recver.length() & 0xFF));
        buffer.put((byte) (senderIp.length() & 0xFF));

        buffer.put(sender.getBytes());
        buffer.put(recver.getBytes());
        buffer.put(senderIp.getBytes());

        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);

        buffer.clear();

        DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(S_ADDR), S_PORT);

        netPacketCache.put(key, packet);

        socket.send(packet);
    }

    /**
     * 客户端根据回执信息，保存至缓存
     *
     * @throws UnknownHostException
     */
    private void handRequest(ByteBuffer buffer) throws UnknownHostException {

        int rslen = buffer.get();
        int rnlen = buffer.get();
        String recverSIp = new String(get(buffer, rslen));
        String recverNIp = new String(get(buffer, rnlen));
        buffer.clear();
        /**
         * 创建一个对于的NetState用于保存对方的内网/外网IP<br>
         * 至于发送的逻辑需自行处理
         */
        String key = NetNatConfig.Type.REQUEST_REPLY + recverNIp;
        if (!netStateCache.containsKey(key)) {
            NetNatConfig.NetState netState = new NetNatConfig.NetState();
            netState.senderSIp = recverSIp;
            netState.senderNIp = InetAddress.getByName(recverNIp);
            netStateCache.put(key, netState);
        }
    }

    /**
     * 客户端调用，发起转发请求
     *
     * @throws IOException
     */
    public void sendForward(byte type, byte[] sendData) throws IOException {
        buffer.clear();

        buffer.put(MAGIC);
        buffer.put(NetNatConfig.Type.FORWARD);
        buffer.put(type);
        // 个数+（IP长度+IP）
        buffer.put((byte) (netStateCache.size() & 0xFF));
        for (NetNatConfig.NetState netState : netStateCache.values()) {
            buffer.put((byte) (netState.senderNIp.getHostAddress().length() & 0xFF));
            buffer.put(netState.senderNIp.getHostAddress().getBytes());
        }

        buffer.put(intToBytes(sendData.length));
        buffer.put(sendData);

        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        buffer.clear();

        DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(S_ADDR), S_PORT);

        socket.send(packet);
    }

    /**
     * 客户端调用，发送数据包
     *
     * @throws IOException
     */
    public void sendData(byte type, byte[] sendData, String remote) throws IOException {
        byte[] data = getSendData(type, sendData);
        DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(remote), S_PORT);
        socket.send(packet);
    }

    /**
     * 构建发送数据包
     */
    private byte[] getSendData(byte type, byte[] sendData) {
        buffer.clear();

        buffer.put(MAGIC);
        buffer.put(type);
        buffer.put(intToBytes(sendData.length));
        buffer.put(sendData);

        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);

        buffer.clear();

        return data;
    }

    /**
     * 客户端调用，发送NAT包
     *
     * @param tos 是否发送至内网地址
     * @throws IOException
     */
    public void sendNat(Boolean tos) throws IOException {
        for (NetState netState : netStateCache.values()) {
            final InetAddress remote = tos ? InetAddress.getByName(netState.senderSIp) : netState.senderNIp;
            buffer.clear();
            buffer.put(MAGIC);
            buffer.put(tos ? Type.NAT_S : Type.NAT_N);
            buffer.put((byte) (remote.getHostName().length() & 0xFF));
            buffer.put(remote.getHostName().getBytes());
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);

            buffer.clear();

            DatagramPacket packet = new DatagramPacket(data, data.length, remote, C_PORT);
            socket.send(packet);
        }
    }

    public void sendNatReply(Boolean tos, String recverIp, NetState netState) throws IOException {
        final InetAddress remote = tos ? InetAddress.getByName(netState.senderSIp) : netState.senderNIp;
        buffer.clear();
        buffer.put(MAGIC);
        buffer.put(tos ? Type.NAT_S_REPLY : Type.NAT_N_REPLY);
        buffer.put((byte) (recverIp.length() & 0xFF));
        buffer.put(recverIp.getBytes());
        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);

        buffer.clear();

        DatagramPacket packet = new DatagramPacket(data, data.length, remote, C_PORT);
        socket.send(packet);
    }

    public void handNat(boolean tos) {
        int rslen = buffer.get();
        String recverIp = new String(get(buffer, rslen));
        buffer.clear();

        for (NetState netState : netStateCache.values()) {
            if (recverIp.equals(netState.senderSIp) || recverIp.equals(netState.senderNIp.getHostName())) {
                try {
                    sendNatReply(tos, recverIp, netState);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    private static byte[] get(ByteBuffer buffer, int l) {
        byte[] read = new byte[l];
        buffer.get(read);
        return read;
    }

    private static byte[] intToBytes(int value) {
        byte[] byte_src = new byte[4];
        byte_src[3] = (byte) ((value & 0xFF000000) >> 24);
        byte_src[2] = (byte) ((value & 0x00FF0000) >> 16);
        byte_src[1] = (byte) ((value & 0x0000FF00) >> 8);
        byte_src[0] = (byte) ((value & 0x000000FF));
        return byte_src;
    }
}
